/*
 * Copyright (c) Ulrich Hilger, http://dev.uhilger.de, all rights reserved.
 *
 * Published under the terms and conditions of the Apache License 2.0,
 * see http://www.apache.org/licenses/LICENSE-2.0
 */

package de.uhilger.servicehub;

import de.uhilger.app.DefaultApplication;
import de.uhilger.app.ui.MainFrame;
import de.uhilger.lib.filesystem.FileRef;
import de.uhilger.lib.filesystem.LocalFileSystem;
import de.uhilger.servicehub.ctrl.action.DirectorySelectAction;
import de.uhilger.servicehub.ctrl.action.FileSelectAction;
import de.uhilger.servicehub.ctrl.action.PrefsAction;
import de.uhilger.servicehub.ctrl.action.StartServerAction;
import de.uhilger.servicehub.ctrl.action.StopServerAction;
import de.uhilger.servicehub.server.EmbeddedJetty;
import de.uhilger.servicehub.server.EmbeddedTomcat;
import de.uhilger.servicehub.server.AppServer;
import de.uhilger.servicehub.server.AppServer.ServerPart;
import de.uhilger.servicehub.server.ServerStatusListener;
import de.uhilger.servicehub.view.AboutDialog;
import de.uhilger.servicehub.view.MainPanel;
import java.awt.BorderLayout;
import java.awt.Container;
import java.beans.PropertyVetoException;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.logging.Level;
import java.util.prefs.Preferences;
import javax.swing.UIManager;

/**
 * <p>Main class of application ServiceHub.</p>
 *
 * @author Ulrich Hilger, <a href="http://dev.uhilger.de" target="_blank">http://dev.uhilger.de</a>
 * @author Published under the terms and conditions of
 * the <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License 2.0</a>
 */
public class ServiceHub extends DefaultApplication implements ServerStatusListener {

  public static final String JAR_PREFIX = "jar:";
  public static final String FILE_PREFIX = "file:";

  private static final String APP_NAME = "ServiceHub";

  private static final String CLASS_EXTENSION = ".class";
  private static final String JAR_EXTENSION = ".jar";
  //private static final String BUILD_DIR = "build";  // Netbeans
  private static final String BUILD_DIR = "bin";   // Eclipse
  private static final String CONF = "conf";
  private static final String LIB = "lib";
  private static final String DEV_DEFAULT_LIB = "dist/lib";
  private static final String DEFAULT = "svchub_def_base";

  public static final String ACTION_DIRECTORY_SELECT = "directorySelect";
  public static final String ACTION_FILE_SELECT = "fileSelect";
  public static final String ACTION_START_SERVER = "startServer";
  public static final String ACTION_STOP_SERVER = "stopServer";
  public static final String ACTION_PREFS = "prefs";

  public static final String PREF_SERVER_PATH = "serverpath";
  public static final String PREF_SRE_PATH = "srepath";
  public static final String PREF_SERVER_PORT = "serverport";
  public static final String PREF_LAST_DEPLOY_FILE = "lastdeployfile";
  public static final String PREF_SERVER_DB_PORT = "serverdbport";
  public static final String PREF_DB_USER = "serverdbb";
  public static final String PREF_DB_CRED = "serverdbc";
  public static final String PREF_DEFAULT_DB_LOGIN = "defaultdblogin";
  public static final String PREF_START_SERVER_ON_LAUNCH = "startserveronlaunch";

  private MainPanel mainPanel;
  private AppServer server;
  private String serverHome;
  private String dbUser;
  private String dbPass;

  /**
   * Create a new object of class ServiceHub
   * @param level  the log level to apply during usage
   */
  public ServiceHub(Level level) {
    super(level, "");
    buildUi();
    //System.out.println("Classpath: " + System.getProperty("java.class.path"));
    Preferences prefs = Preferences.userNodeForPackage(getClass());
    boolean startServerOnLaunch = prefs.getBoolean(PREF_START_SERVER_ON_LAUNCH, true);
    if(startServerOnLaunch) {
      getAction(ACTION_START_SERVER).actionPerformed(null);
    }
  }

  public void setDbLoginEnabled(boolean isEnabled) {
    if(!getServer().isRunning()) {
      mainPanel.getServerPanel().setDbLoginEnabled(isEnabled);
    }
  }

  public void createServerBase() throws Exception {
    LocalFileSystem fs = new LocalFileSystem();
    Preferences prefs = Preferences.userNodeForPackage(getClass());
    String path = prefs.get(PREF_SERVER_PATH, getDefaultPath());
    FileRef confDir = new FileRef(path + File.separator + CONF);
    if(!fs.fileExists(confDir)) {
      FileRef defaultArchive = new FileRef(getDefaultPath() + File.separator + LIB +
              File.separator + DEFAULT + JAR_EXTENSION);
      log("ServiceHub looks for " + defaultArchive.getAbsolutePath(), getClass(), Level.FINEST);
      if(!fs.fileExists(defaultArchive)) {
        defaultArchive = new FileRef(getDefaultPath() + File.separator + DEV_DEFAULT_LIB +
              File.separator + DEFAULT + JAR_EXTENSION);
        log("ServiceHub looks for " + defaultArchive.getAbsolutePath(), getClass(), Level.FINEST);
      }
      fs.extract(defaultArchive, new FileRef(path));
      fs.createFolder(new FileRef(path + File.separator + "conf/deploy_temp"));
      fs.createFolder(new FileRef(path + File.separator + "log"));
    }
  }

  public boolean dbPresent(String absolutePath) {
    return new File(absolutePath).exists();
  }

  /**
   * Show a dialog with information about this application
   */
  @Override
  public void about() {
    MainFrame mf = DefaultApplication.findMainFrame();
    if(mf != null) {
      AboutDialog dlg = new AboutDialog(mf);
      dlg.setLocationRelativeTo(mf);
      dlg.setModal(true);
      dlg.setVisible(true);
    }
  }



  /**
   * Main entry point method
   * @param args the command line arguments, currently the only recognised argument
   * are "finest" and "off".
   * When given they signal ServiceHub to use log Level.FINEST or Level.OFF respectively.
   * Level.INFO will be used without this parameter given.
   */
  public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		}
		catch (Exception e) {
			e.printStackTrace();
		}
    Level level = Level.INFO;
    if(args != null && args.length > 0) {
      for(int i = 0; i < args.length; i++) {
        if(args[i] != null) {
          if(args[i].equals("finest")) {
            level = Level.FINEST;
          } else if(args[i].equals("off")) {
            level = Level.OFF;
          }
        }
      }
    }
    new ServiceHub(level);
  }

  private String searchFile(String fileName, String searchPath) {
    String foundFile = null;
    log("searching for "  + fileName + " in " + searchPath, getClass(), Level.FINEST);
    File[] files = new File(searchPath).listFiles();
    if(files != null) {
      for(int i = 0; i < files.length && foundFile == null; i++) {
        if(files[i].isDirectory()) {
          foundFile = searchFile(fileName, files[i].getAbsolutePath());
        } else {
          if(files[i].getName().equals(fileName)) {
            foundFile = files[i].getAbsolutePath();
          }
        }
      }
    }
    return foundFile;
  }

  private void createServer() {
    Preferences prefs = Preferences.userNodeForPackage(getClass());
    int dbPort = prefs.getInt(PREF_SERVER_DB_PORT, 1527);
    if(searchFile("tomcat-juli.jar", serverHome) != null) {
      server = new AppServer(serverHome, EmbeddedTomcat.class.getName(), dbPort);
    } else {
      server = new AppServer(serverHome, EmbeddedJetty.class.getName(), dbPort);
    }
    String path = prefs.get(PREF_SERVER_PATH, getDefaultPath());
    int port = prefs.getInt(PREF_SERVER_PORT, 8080);
    try {
      if(!server.isRunning()) {
        server.setBase(path);
        server.setPort(port);
        server.setHome(serverHome);
        server.setDbUser(dbUser);
        server.setDbPassword(dbPass);
      }
    } catch (PropertyVetoException ex) {
      logStackTrace(ex, getClass());
    }
    server.addServerStatusListener(this);
    mainPanel.getAppPanel().setServer(server);
    server.addServerStatusListener(mainPanel.getServerPanel());
  }

  public void setServerHome(String serverHome) {
    this.serverHome = serverHome;
  }

  /**
   * Get the server object of this ServiceHub
   * @return  the server object
   */
  public AppServer getServer() {
    if(server == null) {
      createServer();
    }
    return server;
  }

  public void setDbUser(String user) {
    this.dbUser = user;
  }

  public void setDbPass(String pass) {
    this.dbPass = pass;
  }

  /**
   * Set the login data for the user database
   *
   * Call this when the login data (password) is changed after the server was started
   *
   * @param dbUser
   * @param dbPass
   */
  public void setDbLogin(String dbUser, String dbPass) {
    AppServer s = getServer();
    s.setDbUser(dbUser);
    s.setDbPassword(dbPass);
    notifyUserService(dbUser, dbPass);
  }

  /**
   * Let the UserService know that user or password to the database were changed.
   *
   * @param dbUser  current user name to use for connections to the user database
   * @param dbPass  current password to use for connections to the user database
   */
  public void notifyUserService(String dbUser, String dbPass) {
    try {
      String response = setUserServiceDbLogin(dbUser, dbPass);
      log("setDbLogin response=" + response, getClass(), Level.FINEST);
    } catch (Exception ex) {
      logStackTrace(ex, getClass());
    }
  }

  private String setUserServiceDbLogin(String dbUser, String dbPass) throws Exception {
    InputStream in = HttpRequest.get("http://localhost:" + server.getPort() +
            "/UserService/Admin/setDbLogin?dbu=" + dbUser + "&dbp=" + dbPass);
    BufferedReader br = new BufferedReader(new InputStreamReader(in));
    StringBuffer buf = new StringBuffer();
    String line = br.readLine();
    while(line != null) {
      buf.append(line);
      line = br.readLine();
    }
    in.close();
    br.close();
    return buf.toString();
  }

  /**
   * initialize this application's action table
   */
  @Override
  protected void initActions() {
    super.initActions();
    Class c = getClass();
    addAction(c, ACTION_DIRECTORY_SELECT, new DirectorySelectAction(this));
    addAction(c, ACTION_FILE_SELECT, new FileSelectAction(this));
    addAction(c, ACTION_START_SERVER, new StartServerAction(this));
    addAction(c, ACTION_STOP_SERVER, new StopServerAction(this));
    addAction(c, ACTION_PREFS, new PrefsAction(this));
  }

  /**
   * Get the name of this applicaiton
   * @return  the application name
   */
  @Override
  protected String getAppName() {
    return APP_NAME;
  }

  /**
   * Determine whether or not the application can exit. This is called when the user
   * selects to terminate ServiceHub. Whenever something needs to be done before the application
   * can exit this method will return false causing the application to continue running.
   * @return true when this application can exit, false if not
   */
  @Override
  protected boolean canExit() {
    boolean exitOk = super.canExit();
    if(server != null && server.isRunning()) {
      try {
        log("Attempting to stop server upon exit", getClass(), Level.INFO);
        server.stop();
        log("Server stopped.", getClass(), Level.INFO);
      } catch (Exception ex) {
        exitOk = false;
        logStackTrace(ex, getClass());
      }
    }
    return exitOk;
  }

  /**
   * Build the user interface of ServiceHub
   */
  private void buildUi() {
    Container contentPane = findMainFrame().getContentPane();
    contentPane.setLayout(new BorderLayout());
    mainPanel = new MainPanel(this);
    mainPanel.setEnabledAt(MainPanel.TAB_APPS, false);
    mainPanel.setEnabledAt(MainPanel.TAB_ROLES, false);
    mainPanel.setEnabledAt(MainPanel.TAB_USERS, false);
    contentPane.add(mainPanel, BorderLayout.CENTER);
    MainFrame mainFrame = findMainFrame();
    mainFrame.setSize(650, 450);
    mainFrame.setLocationRelativeTo(null);
  }

  /**
   * Get the default context path when non is manually set so far. This will default
   * to the directory where ServiceHub runs with '/webapps' appended.
   * @return  the default context path
   */
  public String getDefaultPath() {
    String defaultPath = null;
    try {
      defaultPath = URLDecoder.decode(getAppPath(), "UTF-8"); // + File.separator + DEFAULT_PATH;
      log("getDefaultPath returns " + defaultPath, getClass(), Level.FINEST);
    } catch (UnsupportedEncodingException ex) {
      logStackTrace(ex, getClass());
    }
    return defaultPath;
  }

  /*
   * url ist entweder
   * jar:file:/C:/Users/ulli2/Documents/NetBeansProjects/ServiceHub/dist/ServiceHub.jar!/de/uhilger/servicehub/ServiceHub.class
   * oder
   * file:/C:/Users/ulli2/Documents/NetBeansProjects/ServiceHub/build/classes/de/uhilger/servicehub/ServiceHub.class
   * das heißt, man muß entweder nach .jar oder nach /build suchen. Wenn man nach build sucht, 
   * hat man gleich im ersten Schritt das home verzeichnis der laufenden Applikation.
   * Wenn man nach .jar sucht, muß man im zweiten Schritt nach / links von
   * der Position von .jar suchen.
   */

  /**
   * Get the path ServiceHub was started from.
   * @return the directory in the file system where ServiceHub was started from
   */
  private String getAppPath() {
    String appPath = null;
    Class c = getClass();
    URL url = c.getResource(c.getSimpleName() + CLASS_EXTENSION);
    String urlStr = url.toString();
    log("getAppPath url is " + urlStr, getClass(), Level.FINEST);
    try {
      int jarPos = urlStr.indexOf(JAR_EXTENSION);
      if(jarPos > -1) {
        int endPos = urlStr.substring(0, jarPos).lastIndexOf(URL_SEPARATOR);
        String prefix = JAR_PREFIX + FILE_PREFIX;
        appPath = new File(urlStr.substring(prefix.length(), endPos)).getAbsolutePath();
      } else {
        appPath = new File(urlStr.substring(FILE_PREFIX.length(),
                urlStr.indexOf(BUILD_DIR)-1)).getAbsolutePath();
      }
      log("getAppPath returns " + appPath, getClass(), Level.FINEST);
    } catch (Exception ex) {
      logStackTrace(ex, getClass());
    }
    return appPath;
  }

  /* ------------------- ServerStatusListener ------------------- */

  /**
   * Handle changes to the server status
   * @param server  the server whose status has changed
   * @param part  the part which was changed
   */
  public void serverStatusChanged(AppServer server, ServerPart part) {
    Preferences prefs = Preferences.userNodeForPackage(getClass());
    switch(part) {
      case BASE:
        prefs.put(PREF_SERVER_PATH, server.getBase());
        break;
      case HOME:
        break;
      case STATUS:
        boolean serverRuns = server.isRunning();
        if(!serverRuns) {
          mainPanel.setSelectedIndex(MainPanel.TAB_SERVER);
        } else {
          mainPanel.setUserStore(server.getUserStore());
        }
        mainPanel.setEnabledAt(MainPanel.TAB_APPS, serverRuns);
        mainPanel.setEnabledAt(MainPanel.TAB_ROLES, serverRuns);
        mainPanel.setEnabledAt(MainPanel.TAB_USERS, serverRuns);
        break;
      case HTTP_PORT:
        prefs.putInt(PREF_SERVER_PORT, server.getPort());
        break;
      case JDBC_PORT:
        prefs.putInt(PREF_SERVER_DB_PORT, server.getDbPort());
        break;
    }
  }
}
